package com.sunlands.deskmate.service;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.sunlands.deskmate.client.*;
import com.sunlands.deskmate.domain.MessageDO;
import com.sunlands.deskmate.domain.MessageRecordDO;
import com.sunlands.deskmate.domain.MessageSystemDO;
import com.sunlands.deskmate.enums.MessageType;
import com.sunlands.deskmate.repository.MessageRecordRepository;
import com.sunlands.deskmate.repository.MessageRepository;
import com.sunlands.deskmate.repository.MessageSystemRepository;
import com.sunlands.deskmate.util.BeanPropertiesUtil;
import com.sunlands.deskmate.vo.*;
import com.sunlands.deskmate.vo.response.BusinessResult;
import com.sunlands.deskmate.vo.response.PageResultVO;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author shixiaopeng
 */
@Slf4j
@Service
public class MessageService implements BeanPropertiesUtil {

    @Value("${message.type}")
    private String messageType;

    @Value("${init.user-id}")
    private String initUserId;

    /**
     * 黑名单校验的type
     */
    private final String[] black_type = {"700", "798", "799"};

    /**
     * 根据userId和type定义一条消息
     */
    private final String[] userId_type_type = {"760", "798", "799"};

    /**
     * 根据userId和business定义一条消息
     */
    private final String[] userId_businessId_type;

    {
        userId_businessId_type = new String[]{"1", "2", "3", "71", "74", "75"};
    }

    @Transactional(rollbackFor = Exception.class, isolation = Isolation.REPEATABLE_READ)
    public void createPerson(MessageDTO messageDTO) {
        //黑名单校验
        List<String> blackType = Lists.newArrayList(black_type);

        MessageDO messageDO = new MessageDO();
        copyNonNullProperties(messageDTO, messageDO);
        ObjectMapper mapper = new ObjectMapper();
        /**
         * 新加
         */

        try {
            Map<String, String> extras = new HashMap<>();
            if (messageDTO.getExtras() != null) {
                Map<String, String> messageDTOExtras = messageDTO.getExtras();
                extras.putAll(messageDTOExtras);
                messageDO.setExtras(mapper.writeValueAsString(extras));
            }
            if (messageDTO.getType().equals("750")) {
                //房间信息

                RoomSimpleInfo simpleInfo = tzLiveVideoService.getRoomSimpleInfo(Long.parseLong(messageDTO.getBusinessId())).getData();
                extras.put("roomName", simpleInfo.getRoomName());
                extras.put("roomAvatar", simpleInfo.getRoomAvatar());
                messageDO.setExtras(mapper.writeValueAsString(extras));
            } else if (messageDTO.getType().equals("700")) {
                //请求个人信息
                UsersVO usersVO = tzUserCenterService.getUserInfoById(Long.parseLong(messageDTO.getBusinessId())).getData();
                extras.put("fromName", usersVO.getName());
                extras.put("fromAvatar", usersVO.getAvatarUrl());
                messageDO.setExtras(mapper.writeValueAsString(extras));

            } else if (messageDTO.getType().startsWith("2")) {
                //请求群组信息
                GroupSimpleInfo groupSimpleInfo = deskMateGroupService.getGroupSimpleInfo(messageDTO.getBusinessId()).getData();
                extras.put("fromName", groupSimpleInfo.getGroupName());
                extras.put("fromAvatar", groupSimpleInfo.getAvatarFile());
                extras.put("groupType", groupSimpleInfo.getType() + "");
                messageDO.setExtras(mapper.writeValueAsString(extras));
            }

        } catch (JsonProcessingException e) {
            log.info("extras writeValueAsString error");
        }

        List<MessageDO> messageDOList = new ArrayList<>();
        //小书童--即系统消息
        List<MessageSystemDO> messageSystemDOList = new ArrayList<>();
        Boolean isSystem = false;
        if (messageDTO.getType().startsWith(messageType)) {
            isSystem = true;
        }

        Boolean useridBusinessidTypeFlag = false;
        List<String> userIdBusinessIdType = Lists.newArrayList(userId_businessId_type);
        for (String str : userIdBusinessIdType) {
            if (messageDO.getType().startsWith(str)) {
                useridBusinessidTypeFlag = true;
                break;
            }
        }

        List<String> userIdTypeType = Lists.newArrayList(userId_type_type);
        Boolean useridTypeTypeFlag = false;
        if (userIdTypeType.contains(messageDO.getType())) {
            useridTypeTypeFlag = true;
        }

        List<Integer> userIds = messageDTO.getUserIds();
        for (Integer userId : userIds) {
            if (blackType.contains(messageDTO.getType())) {
                BusinessResult<List<UserBlacklist>> result = tzUserFriendService.checkBlacklist((long) userId, Long.parseLong(messageDTO.getBusinessId()));
                if (result.getData().size() > 0) {
                    continue;
                }
            }

            MessageDO messageDODB;
            if (useridBusinessidTypeFlag) {
                redisFairLock(messageDTO, messageDO, messageDOList, messageSystemDOList, isSystem, userId);
            } else if (useridTypeTypeFlag) {
                messageDODB = messageRepository.findFirstByUserIdAndType(userId, messageDO.getType());
                saveOrUpdate(messageDTO, messageDO, messageDOList, messageSystemDOList, isSystem, userId, messageDODB);
            } else {
                messageDODB = messageRepository.findAllByUserIdAndBusinessIdAndType(userId, messageDO.getBusinessId(), messageDO.getType());
                saveOrUpdate(messageDTO, messageDO, messageDOList, messageSystemDOList, isSystem, userId, messageDODB);
            }


        }
        //调用消息通知接口
        noticeAndSave(messageDTO, userIds, messageSystemDOList);
    }

    private void redisFairLock(MessageDTO messageDTO, MessageDO messageDO, List<MessageDO> messageDOList, List<MessageSystemDO> messageSystemDOList, Boolean isSystem, Integer userId) {
        MessageDO messageDODB;
        RLock fairLock = redissonClient.getFairLock(userId + messageDO.getBusinessId());
        try {
            int waitTimeInSeconds = 3;
            int leaseTimeInSeconds = 6;
            if (fairLock.tryLock(waitTimeInSeconds, leaseTimeInSeconds, TimeUnit.SECONDS)) {
                messageDODB = messageRepository.findFirstByUserIdAndBusinessIdAndTypeIsNot(userId, messageDO.getBusinessId(), "700");
                saveOrUpdate(messageDTO, messageDO, messageDOList, messageSystemDOList, isSystem, userId, messageDODB);
            } else {
                log.error("getLock fail ,userId : {}, messageDO.getBusinessId() :{}", userId, messageDO.getBusinessId());
                //无限重试
                redisFairLock(messageDTO, messageDO, messageDOList, messageSystemDOList, isSystem, userId);
            }
        } catch (InterruptedException e) {
            log.error("", e);
        } finally {
            if (fairLock.isLocked()) {
                fairLock.unlock();
            }
        }
    }

    private void saveOrUpdate(MessageDTO messageDTO, MessageDO messageDO, List<MessageDO> messageDOList, List<MessageSystemDO> messageSystemDOList, Boolean isSystem, Integer userId, MessageDO messageDODB) {
        if (messageDODB != null) {
            //修改
            messageDODB.setUnreadCount(messageDODB.getUnreadCount() + 1);
            messageDODB.setIsRead(false);
            messageDODB.setContent(messageDTO.getContent());
            messageDODB.setTitle(messageDTO.getTitle());
            if (!messageDODB.getType().equals(messageDO.getType())) {
                messageDODB.setType(messageDO.getType());
            }
            messageDODB.setBusinessId(messageDO.getBusinessId());
            messageDODB.setExtras(messageDO.getExtras());
            messageDOList.add(messageDODB);
        } else {
            MessageDO messageDOSave = new MessageDO();
            copyNonNullProperties(messageDO, messageDOSave);

            //新增
            messageDOSave.setUserId(userId);
            messageDOSave.setUnreadCount(1);
            messageDOList.add(messageDOSave);
        }
        if (isSystem) {
            log.info("系统消息 {}", isSystem);
            MessageSystemDO messageSystemDO = new MessageSystemDO();
            copyNonNullProperties(messageDTO, messageSystemDO);
            messageSystemDO.setUserId(userId);
            messageSystemDOList.add(messageSystemDO);
        }
        messageRepository.save(messageDOList);
    }

    @Transactional(rollbackFor = Exception.class, isolation = Isolation.REPEATABLE_READ)
    public void createGroup(MessageDTO messageDTO) {
        MessageDO messageDO = new MessageDO();
        copyNonNullProperties(messageDTO, messageDO);
        ObjectMapper mapper = new ObjectMapper();
        /**
         * 新加
         */
        Map<String, String> extras = new HashMap<>();
        try {
            if (messageDTO.getExtras() != null) {
                Map<String, String> messageDTOExtras = messageDTO.getExtras();
                extras.putAll(messageDTOExtras);
                messageDO.setExtras(mapper.writeValueAsString(extras));
            }
            if (messageDTO.getType().equals("750")) {
                //房间信息

                RoomSimpleInfo simpleInfo = tzLiveVideoService.getRoomSimpleInfo(Long.parseLong(messageDTO.getBusinessId())).getData();
                extras.put("roomName", simpleInfo.getRoomName());
                extras.put("roomAvatar", simpleInfo.getRoomAvatar());
                messageDO.setExtras(mapper.writeValueAsString(extras));
            } else if (messageDTO.getType().equals("700")) {
                //请求个人信息
                UsersVO usersVO = tzUserCenterService.getUserInfoById(Long.parseLong(messageDTO.getBusinessId())).getData();
                extras.put("fromName", usersVO.getName());
                extras.put("fromAvatar", usersVO.getAvatarUrl());
                messageDO.setExtras(mapper.writeValueAsString(extras));

            } else if (messageDTO.getType().startsWith("2")) {
                //请求群组信息
                GroupSimpleInfo groupSimpleInfo = deskMateGroupService.getGroupSimpleInfo(messageDTO.getBusinessId()).getData();
                extras.put("fromName", groupSimpleInfo.getGroupName());
                extras.put("fromAvatar", groupSimpleInfo.getAvatarFile());
                extras.put("groupType", groupSimpleInfo.getType() + "");
                messageDO.setExtras(mapper.writeValueAsString(extras));
            }

        } catch (JsonProcessingException e) {
            log.info("extras writeValueAsString error");
        }

        //小书童--即系统消息
        List<MessageSystemDO> messageSystemDOList = new ArrayList<>();
        Boolean isSystem = false;
        if (messageDTO.getType().startsWith(messageType)) {
            isSystem = true;
        }

        Boolean useridBusinessidTypeFlag = false;
        List<String> userIdBusinessIdType = Lists.newArrayList(userId_businessId_type);
        for (String str : userIdBusinessIdType) {
            if (messageDO.getType().startsWith(str)) {
                useridBusinessidTypeFlag = true;
                break;
            }
        }

        List<String> userIdTypeType = Lists.newArrayList(userId_type_type);
        Boolean useridTypeTypeFlag = false;
        if (userIdTypeType.contains(messageDO.getType())) {
            useridTypeTypeFlag = true;
        }

        //根据群id查询群用户列表
        log.info("messageDTO.getBusinessId() = {} ", messageDTO.getBusinessId());
        BusinessResult<List<GroupUserVO>> groupUserByGroupId = deskMateGroupService.getGroupUserByGroupId(messageDTO.getBusinessId());
        if (groupUserByGroupId != null && !groupUserByGroupId.getData().isEmpty()) {
            List<Integer> userIds = groupUserByGroupId.getData().stream().map(groupUserVO -> Integer.parseInt(groupUserVO.getUserId())).collect(Collectors.toList());
            log.info("userIds = {} ", userIds);
            if (messageDTO.getExcludeUserIds() != null) {
                userIds.removeAll(messageDTO.getExcludeUserIds());
            }
            log.info("userIds.remove(messageDTO.getUserId()) = {} ", userIds);
            List<MessageDO> messageDOList = new ArrayList<>();


            for (Integer userId : userIds) {
                MessageDO messageDODB;
                if (useridBusinessidTypeFlag) {
                    redisFairLock(messageDTO, messageDO, messageDOList, messageSystemDOList, isSystem, userId);
                } else if (useridTypeTypeFlag) {
                    messageDODB = messageRepository.findFirstByUserIdAndType(userId, messageDO.getType());
                    saveOrUpdate(messageDTO, messageDO, messageDOList, messageSystemDOList, isSystem, userId, messageDODB);
                } else {
                    messageDODB = messageRepository.findAllByUserIdAndBusinessIdAndType(userId, messageDO.getBusinessId(), messageDO.getType());
                    saveOrUpdate(messageDTO, messageDO, messageDOList, messageSystemDOList, isSystem, userId, messageDODB);
                }
            }
            log.info("小书童messageSystemDOList ：{}", messageSystemDOList);
            noticeAndSave(messageDTO, userIds, messageSystemDOList);


        }
    }

    /**
     * 给所有用户发消息
     *
     * @param messageDTO
     */
    public void createMessageForAll(MessageDTO messageDTO) {
        MessageDO messageDO = new MessageDO();
        copyNonNullProperties(messageDTO, messageDO);

        Integer pageSize = 100;
        for (int page = 1; ; page++) {
            PageResultVO<UsersExtInfoVO> data = tzUserCenterService.userExtPage(page, pageSize).getData();
            if (Objects.isNull(data)) {
                break;
            }
            List<UsersExtInfoVO> rows = data.getRows();
            if (Objects.isNull(rows) || rows.isEmpty()) {
                break;
            }

            List<MessageDO> messageDOList = new ArrayList<>();
            //小书童--即系统消息
            List<MessageSystemDO> messageSystemDOList = new ArrayList<>();
            List<Integer> userIds = new ArrayList<>();
            rows.forEach(usersExtInfoVO -> {

//                int compareVersion = compareVersion(usersExtInfoVO.getVersion(), "2.3");
//                if (compareVersion >= 0) {
                    userIds.add(usersExtInfoVO.getUserId());
                    MessageDO messageDODB = messageRepository.findFirstByUserIdAndType(usersExtInfoVO.getUserId(), messageDO.getType());
                    saveOrUpdate(messageDTO, messageDO, messageDOList, messageSystemDOList, true, usersExtInfoVO.getUserId(), messageDODB);
//                }
            });
            log.info("当页符合添加的用户数 {}", userIds.size());
            noticeAndSave(messageDTO, userIds, messageSystemDOList);

        }
    }

    private void noticeAndSave(MessageDTO messageDTO, List<Integer> userIds, List<MessageSystemDO> messageSystemDOList) {
        CompletableFuture.runAsync(() -> {
            //调用消息通知接口
            MsgChangeInformEntity msgChangeInformEntity = new MsgChangeInformEntity();
            msgChangeInformEntity.setType("999");
            List<String> stringList = userIds.stream().map(userId -> String.valueOf(userId)).collect(Collectors.toList());
            msgChangeInformEntity.setUserIds(stringList);
            deskMateSocketService.inform(msgChangeInformEntity);

        });
        CompletableFuture.runAsync(() -> {
            MessageRecordDO messageRecordDO = new MessageRecordDO();
            copyNonNullProperties(messageDTO, messageRecordDO);
            if (messageDTO.getExcludeUserIds() != null) {
                messageRecordDO.setExcludeUserIds(String.join(",", messageDTO.getExcludeUserIds().toString()));
            }
            if (messageDTO.getUserIds() != null) {
                messageRecordDO.setUserIds(String.join(",", messageDTO.getUserIds().toString()));
            }
            messageRecordRepository.save(messageRecordDO);
            messageSystemRepository.save(messageSystemDOList);
        });
    }


    public List<Message> getMessageList(Integer userId) {
        RLock fairLock = redissonClient.getFairLock("userId:" + userId);
        int waitTimeInSeconds = 3;
        int leaseTimeInSeconds = 6;
        try {
            if (fairLock.tryLock(waitTimeInSeconds, leaseTimeInSeconds, TimeUnit.SECONDS)) {

                List<MessageDO> messageDOList = messageRepository.findAllByUserIdAndIsReadOrderByUpdateDateTimeDesc(userId, false);

                List<Message> messageList = new ArrayList<>();
                for (MessageDO messageDO : messageDOList) {
                    Message message = new Message();
                    copyNonNullProperties(messageDO, message);
                    ObjectMapper mapper = new ObjectMapper();
                    if (messageDO.getExtras() != null) {
                        try {
                            Map<String, String> map = mapper.readValue(messageDO.getExtras(), Map.class);
                            message.setExtras(map);
                        } catch (IOException e) {
                            log.info("extras readValue error");
                        }
                    }

                    messageList.add(message);
                }
                //修改为已读
                List<MessageDO> list = messageDOList.stream().peek(messageDO -> {
                    messageDO.setIsRead(true);
                    messageDO.setUnreadCount(0);
                }).collect(Collectors.toList());
                messageRepository.save(list);
                log.info("用户 ：{} ,消息接口返回数据 : {}", userId, messageList);
                return messageList;
            } else {
                log.error("method getMessageList getLock fail ,userId : {}", userId);
            }
        } catch (InterruptedException e) {
            log.error("", e);
            e.printStackTrace();
        } finally {
            if (fairLock.isLocked()) {
                fairLock.unlock();
            }
        }
        return Collections.emptyList();
    }

    public List<Message> test(Integer userId) {
        RLock fairLock = redissonClient.getFairLock("userId:" + userId);
        int waitTimeInSeconds = 3;
        int leaseTimeInSeconds = 6;
        try {
            if (fairLock.tryLock(waitTimeInSeconds, leaseTimeInSeconds, TimeUnit.SECONDS)) {

                List<MessageDO> messageDOList = messageRepository.findAllByUserIdAndIsReadOrderByUpdateDateTimeDesc(0, false);

                List<Message> messageList = new ArrayList<>();
                for (MessageDO messageDO : messageDOList) {
                    Message message = new Message();
                    copyNonNullProperties(messageDO, message);
                    ObjectMapper mapper = new ObjectMapper();
                    if (messageDO.getExtras() != null) {
                        try {
                            Map<String, String> map = mapper.readValue(messageDO.getExtras(), Map.class);
                            message.setExtras(map);
                        } catch (IOException e) {
                            log.info("extras readValue error");
                        }
                    }

                    messageList.add(message);
                }
                log.info("用户 ：{} ,消息接口返回数据 : {}", userId, messageList);
                return messageList;
            } else {
                log.error("method getMessageList getLock fail ,userId : {}", userId);
            }
        } catch (InterruptedException e) {
            log.error("", e);
            e.printStackTrace();
        } finally {
            if (fairLock.isLocked()) {
                fairLock.unlock();
            }
        }
        return Collections.emptyList();
    }

    public PageResultVO<MessageSystemDO> findSystemMessagePages(Integer userId, Integer page, Integer pageSize) {
        Pageable pageable = new PageRequest(page - 1, pageSize);
        Page<MessageSystemDO> messageSystemDOS = messageSystemRepository.findAllByUserIdOrderByCreateDateTimeDesc(userId, pageable);

        PageResultVO<MessageSystemDO> pageResultVO = new PageResultVO<>();
        List<MessageSystemDO> systemDOList = new ArrayList(messageSystemDOS.getContent());
        Collections.reverse(systemDOList);
        pageResultVO.setRows(systemDOList);
        pageResultVO.setTotal(messageSystemDOS.getTotalElements());

        return pageResultVO;
    }

    public List<Message> liveState(Long userId) {
        List<Long> userIds = tzUserFriendService.friends(userId).getData().stream().map(TzUserFriendInfo::getFriendsUserId).collect(Collectors.toList());
        if (userIds.isEmpty()) {
            return new ArrayList<>();
        }
        List<UserLiveStateVO> liveStates = tzLiveVideoService.findLiveState(userIds).getData();

        List<Message> messages = liveStates.stream().map(liveState -> {
            Map<String, String> map = new HashMap<>();
            map.put("roomName", liveState.getRoomName());
            map.put("roomAvatar", liveState.getRoomAvatar());

            return Message.builder().businessId(liveState.getRoomId().toString())
                    .type(MessageType.DYNAMIC_CREATE_ROOM.getType())
                    .extras(map)
                    .build();
        }).collect(Collectors.toList());
        log.debug("道友连麦列表 messages = {}", messages);
        return messages;
    }

    /**
     * 清洗数据使用
     */
    public void cleanData() {
        List<String> userIdBusinessIdType = Lists.newArrayList(userId_businessId_type);
        for (String str : userIdBusinessIdType) {
            List<MessageDO> messageDOList = messageRepository.findBySql(str + "%");

            List<MessageDO> deleteList = new ArrayList<>();

            MessageDO before = new MessageDO();
            for (MessageDO messageDO : messageDOList) {
                if (messageDO.getBusinessId().equals(before.getBusinessId()) && messageDO.getUserId().equals(before.getUserId())) {
                    deleteList.add(messageDO);
                }
                before = messageDO;
            }

            messageRepository.delete(deleteList);
        }

    }

    /**
     * 版本号比较
     *
     * @param v1
     * @param v2
     * @return 0代表相等，1代表左边大，-1代表右边大
     * Utils.compareVersion("1.0.358_20180820090554","1.0.358_20180820090553")=1

    public static int compareVersion(String v1, String v2) {
        if (v1.equals(v2)) {
            return 0;
        }
        String[] version1Array = v1.split("[._]");
        String[] version2Array = v2.split("[._]");
        int index = 0;
        int minLen = Math.min(version1Array.length, version2Array.length);
        long diff = 0;

        while (index < minLen
                && (diff = Long.parseLong(version1Array[index])
                - Long.parseLong(version2Array[index])) == 0) {
            index++;
        }
        if (diff == 0) {
            for (int i = index; i < version1Array.length; i++) {
                if (Long.parseLong(version1Array[i]) > 0) {
                    return 1;
                }
            }

            for (int i = index; i < version2Array.length; i++) {
                if (Long.parseLong(version2Array[i]) > 0) {
                    return -1;
                }
            }
            return 0;
        } else {
            return diff > 0 ? 1 : -1;
        }
    }
     */
    private final MessageRepository messageRepository;
    private final DeskMateGroupService deskMateGroupService;
    private final MessageRecordRepository messageRecordRepository;
    private final MessageSystemRepository messageSystemRepository;
    private final DeskMateSocketService deskMateSocketService;
    private final UserFriendService tzUserFriendService;
    private final RedissonClient redissonClient;
    private final UserCenterService tzUserCenterService;
    private final LiveVideoService tzLiveVideoService;

    public MessageService(MessageRepository messageRepository, DeskMateGroupService deskMateGroupService, MessageRecordRepository messageRecordRepository, MessageSystemRepository messageSystemRepository, DeskMateSocketService deskMateSocketService, UserFriendService tzUserFriendService, RedissonClient redissonClient, UserCenterService tzUserCenterService, LiveVideoService tzLiveVideoService) {
        this.messageRepository = messageRepository;
        this.deskMateGroupService = deskMateGroupService;
        this.messageRecordRepository = messageRecordRepository;
        this.messageSystemRepository = messageSystemRepository;
        this.deskMateSocketService = deskMateSocketService;
        this.tzUserFriendService = tzUserFriendService;
        this.redissonClient = redissonClient;
        this.tzUserCenterService = tzUserCenterService;
        this.tzLiveVideoService = tzLiveVideoService;
    }
}
